#ifndef I_LIB_IP_I_PYRAMID_H
#define I_LIB_IP_I_PYRAMID_H

#include <vector>

#include "../core/i_blas.h"
#include "../core/i_alloc.h"
#include "../core/i_struct.h"
#include "i_resize.h"

namespace idl
{
    template <typename DATA_TYPE>
    class MultiScaleLumPyramid
    {
    public:
        MultiScaleLumPyramid();
        MultiScaleLumPyramid(const MultiScaleLumPyramid<DATA_TYPE>& pyramid);
        MultiScaleLumPyramid& operator=(const MultiScaleLumPyramid<DATA_TYPE>& pyramid);
        
        virtual ~MultiScaleLumPyramid();
        bool init(int width, int height, int scale);
        const DATA_TYPE* const_data() const 
        { 
            return (const DATA_TYPE*)_memory;
        };
        const DATA_TYPE* const_data(unsigned int scale) const;
        DATA_TYPE* data() 
        { 
            return _memory;
        };
        DATA_TYPE* data(unsigned int scale);
        const DATA_TYPE** const_data2() const;
        const DATA_TYPE** const_data2(unsigned int scale) const;
        DATA_TYPE** data2();
        DATA_TYPE** data2(unsigned int scale);
        bool initialized() const 
        { 
            return _initialized;
        };
        int nr_scale() const 
        { 
            return _nr_scale;
        };
        int width() const;
        int width(unsigned int scale) const;
        int height() const;
        int height(unsigned int scale) const;
        int size(unsigned int scale) const;
        int size() const 
        { 
            return _length;
        };
        void bottom_up();/*build the image pyramid from bottom up, large - small*/
        void reset(DATA_TYPE value);
        void reset(unsigned int scale, DATA_TYPE value);
        //int top_dowm();/*build the image pyramid from top down, small - large, not ready to use yet*/
    private:
        void cleanup();   
    private:
        bool _initialized;
        int _nr_scale;
        int _length;
        std::vector<Pair<int, int> > _dimensions;
        std::vector<DATA_TYPE**    > _data;
        DATA_TYPE* _memory;
    };

    template <typename DATA_TYPE>
    MultiScaleLumPyramid<DATA_TYPE>::MultiScaleLumPyramid() :
        _initialized(false), _nr_scale(0), _length(0), _memory(NULL){}
    
    template <typename DATA_TYPE>
    MultiScaleLumPyramid<DATA_TYPE>::MultiScaleLumPyramid(
        const MultiScaleLumPyramid<DATA_TYPE>& pyramid)
    {
        int w, h, i;
        _initialized = false;
        _nr_scale = _length = 0;
        _memory = NULL;
        
        if (pyramid.initialized())
        {
            _nr_scale = pyramid.nr_scale();
            _length = pyramid.size();
            _memory = i_alloc<DATA_TYPE>(_length);
        
            if (_memory)/*allocated enough system memory*/
            {
                w = pyramid.width();
                h = pyramid.height();
                for (i = 0; i < _nr_scale; ++i)
                {
                    Pair<int, int> dim = i_make_pair<int, int>(w, h);
                    _dimensions.push_back(dim);
                    w = (w >> 1);
                    h = (h >> 1);
                }

                DATA_TYPE *p_mem = _memory;
                _data.resize(_nr_scale);

                for (i = 0; i < _nr_scale; ++i)
                {
                    w = _dimensions[i].first;
                    h = _dimensions[i].second;
                    _data[i] = new(std::nothrow) DATA_TYPE*[h];
                    if (_data[i] == NULL)
                    {
                        break; /*break if memory allocation failed*/
                    }
                    i_make_reference<DATA_TYPE>(p_mem, _data[i], h, w);
                    p_mem += w*h;
                }

                if (_nr_scale == i)
                {
                    _initialized = true;
                    i_copy(pyramid.const_data(), _memory, _length);
                }
                else
                {
                    cleanup();
                }
            }/*if memory != NULL*/
            else
            {
                cleanup(); /*cleanup if memory allocation failed*/
            }
        }/*if pyramid.initialized() == TRUE*/
    }
    
    template <typename DATA_TYPE>
    MultiScaleLumPyramid<DATA_TYPE>& MultiScaleLumPyramid<DATA_TYPE>::
        operator=(const MultiScaleLumPyramid<DATA_TYPE>& pyramid)
    {
        if (this == &pyramid)/*check self-assignment*/
        {
            return(*this);
        }
        if (this->init(pyramid.width(), pyramid.height(), pyramid.nr_scale()))
        {
            i_copy(pyramid.const_data(), this->_memory, pyramid.size());
        }
        return(*this);
    }

    template <typename DATA_TYPE>
    MultiScaleLumPyramid<DATA_TYPE>::~MultiScaleLumPyramid()
    {
        cleanup();
    }
    
    template <typename DATA_TYPE>
    bool MultiScaleLumPyramid<DATA_TYPE>::init(int width, int height, int scale)
    {
        int i, w, h;
        _length = 0; /*no memory copy for the first scale, using the original image data address*/
        if (!width || !height || !scale)
        {
            return false;
        }
        
        if (_initialized /*memory has been alloced*/)
        {
            if (scale != _nr_scale || width != _dimensions.front().first
                || height != _dimensions.front().second)
            {
                cleanup();
            }
            else
            {
                return true;
            }
        }
        
        w = width;
        h = height;
        for (i = 0; i < scale; ++i)
        {
            if (w * h < 16/*min. image dimension is 16*/)
            {
                break;
            }
            Pair<int, int> dim = i_make_pair<int, int>(w, h);
            _dimensions.push_back(dim);
            _length += (w * h);
            w = (w >> 1);
            h = (h >> 1);
        }
        
        _nr_scale = (int)_dimensions.size();/*actual # of scales*/
        
        if (_nr_scale == 0)
        {
            cleanup();
            return false;
        }
        
        _memory = i_alloc<DATA_TYPE>(_length);
        if (_memory == NULL)
        {
            cleanup();
            return false;
        }
        
        DATA_TYPE *p_mem = _memory;
        _data.resize(_nr_scale);
        
        for (i = 0; i < _nr_scale; ++i)
        {
            w = _dimensions[i].first;
            h = _dimensions[i].second;
            _data[i] = new(std::nothrow) DATA_TYPE*[h];
            if (_data[i] == NULL)
            {
                cleanup();
                return false;
            }
            i_make_reference<DATA_TYPE>(p_mem, _data[i], h, w);
            p_mem += w*h;
        }
        _initialized = true;
        return true;
    }
    
    template <typename DATA_TYPE>
    void MultiScaleLumPyramid<DATA_TYPE>::cleanup()
    {
        i_free<DATA_TYPE>(_memory);
        for (int i = 0; i < _nr_scale; ++i)
        {
            if (_data[i] != NULL)
            {
                delete[] _data[i];
                _data[i] = NULL;
            }
        }
        _dimensions.clear();
        _data.clear();
        _nr_scale = 0;
        _length = 0;
        _initialized = false;
    }

    template <typename DATA_TYPE>
    const DATA_TYPE* MultiScaleLumPyramid<DATA_TYPE>::const_data(unsigned int scale) const
    {
        return (scale < (unsigned int)_nr_scale) ? (const DATA_TYPE*)_data[scale][0] : NULL;
    }
  
    template <typename DATA_TYPE>
    DATA_TYPE* MultiScaleLumPyramid<DATA_TYPE>::data(unsigned int scale)
    {
        return (scale < (unsigned int)_nr_scale) ? _data[scale][0] : NULL;
    }

    template <typename DATA_TYPE>
    const DATA_TYPE** MultiScaleLumPyramid<DATA_TYPE>::const_data2() const
    {
        return (const DATA_TYPE**)_data[0];
    }

    template <typename DATA_TYPE>
    const DATA_TYPE** MultiScaleLumPyramid<DATA_TYPE>::const_data2(unsigned int scale) const
    {
        return (scale < (unsigned int)_nr_scale) ? (const DATA_TYPE**)_data[scale] : NULL;
    }

    template <typename DATA_TYPE>
    DATA_TYPE** MultiScaleLumPyramid<DATA_TYPE>::data2()
    {
        return (DATA_TYPE**)_data[0];
    }

    template <typename DATA_TYPE>
    DATA_TYPE** MultiScaleLumPyramid<DATA_TYPE>::data2(unsigned int scale)
    {
        return (scale < (unsigned int)_nr_scale) ? (DATA_TYPE**)_data[scale] : NULL;
    }

    /*the base width of the pyramid*/
    template <typename DATA_TYPE>
    int MultiScaleLumPyramid<DATA_TYPE>::width() const
    {
        return (_nr_scale > 0) ? _dimensions[0].first : 0;
    }

    template <typename DATA_TYPE>
    int MultiScaleLumPyramid<DATA_TYPE>::width(unsigned int scale) const
    {
        return (scale < (unsigned int)_nr_scale) ? _dimensions[scale].first : 0;
    }

    /*the base height of the pyramid*/
    template <typename DATA_TYPE>
    int MultiScaleLumPyramid<DATA_TYPE>::height() const
    {
        return (_nr_scale > 0) ? _dimensions[0].second : 0;
    }

    template <typename DATA_TYPE>
    int MultiScaleLumPyramid<DATA_TYPE>::height(unsigned int scale) const
    {
        return (scale < (unsigned int)_nr_scale) ? _dimensions[scale].second : 0;
    }
  
    template <typename DATA_TYPE>
    int MultiScaleLumPyramid<DATA_TYPE>::size(unsigned int scale) const
    {
        return (scale < (unsigned int)_nr_scale) ? 
            (_dimensions[scale].first * _dimensions[scale].second) : 0;
    }

    template <typename DATA_TYPE>
    void MultiScaleLumPyramid<DATA_TYPE>::bottom_up()
    {
        if (!_initialized)
        {
            return;
        }
        for (int i = 0; i < _nr_scale - 1; ++i)
        {
            i_half_lum<DATA_TYPE>(_data[i][0], _data[i + 1][0], 
                _dimensions[i].first, _dimensions[i].second);
        }
    }

    template <typename DATA_TYPE>
    void MultiScaleLumPyramid<DATA_TYPE>::reset(DATA_TYPE value)
    {
        if (_initialized)
        {
            i_fill(_memory, _length, value);
        }
    }

    template <typename DATA_TYPE>
    void MultiScaleLumPyramid<DATA_TYPE>::reset(unsigned int scale, DATA_TYPE value)
    {
        if (_initialized && scale < (unsigned int)_nr_scale)
        {
            i_fill(_data[scale][0], (_dimensions[scale].first*_dimensions[scale].second), value);
        }
    }

}/*namespace idl*/

#endif